package com.iteaj.iot.plc.siemens;

import com.iteaj.iot.client.ClientConnectProperties;
import com.iteaj.iot.client.protocol.ClientSocketProtocol;
import com.iteaj.iot.consts.ExecStatus;
import com.iteaj.iot.plc.*;
import com.iteaj.iot.utils.ByteUtil;

import java.util.ArrayList;
import java.util.List;

/**
 * 西门子S200序列
 */
public class SiemensS7Protocol extends PlcClientProtocol<SiemensS7Message> {

    private byte[] data;

    /**
     * 使用默认的客户端
     */
    public SiemensS7Protocol() {
        super(2);
    }

    /**
     * 使用指定配置的客户端
     * @see SiemensConnectProperties#connectKey() 如果客户端不存在则重新创建
     * @see SiemensS7Component#createNewClient(ClientConnectProperties)
     * @param properties 要操作的plc配置
     */
    public SiemensS7Protocol(ClientConnectProperties properties) {
        super(properties, 2);
    }

    @Override
    public ClientSocketProtocol buildRequestMessage() {
        if(this.requestMessage() != null) {
            return this;
        }

        return super.buildRequestMessage();
    }

    @Override
    protected SiemensS7Message doBuildRequestMessage() {
        SiemensMessageBody body; SiemensMessageHeader header;

        // 写plc
        WriteAddress writeAddress = this.getWriteAddress();
        if(writeAddress != null) {
            body = SiemensMessageBody.buildWriteBody(writeAddress);
            header = SiemensMessageHeader.buildWriteHeader(writeAddress.getData().length);
        } else { // 读plc
            body = SiemensMessageBody.buildReadBody(this.getBatchAddress());
            header = SiemensMessageHeader.buildReadHeader((short) this.getBatchAddress().size());
        }

        return new SiemensS7Message(header, body);
    }

    @Override
    public void doBuildResponseMessage(SiemensS7Message responseMessage) {
        if(getExecStatus() == ExecStatus.success) {
            byte[] message = responseMessage.getMessage();
            byte rwId = message[19];
            if(rwId == 4) { // 读功能的响应
                if((message[21] & 0xFF) == 0xFF) {
                    this.setCmdStatus(true, "读取成功");
                    this.data = ByteUtil.subBytes(message, 25);
                } else { // 读取错误
                    this.setCmdStatus(false, "读取失败");
                }
            } else if(rwId == 5) { // 写指令的响应
                // 写入失败
                if((message[message.length - 1] & 0xFF) != 0xFF) {
                    this.setCmdStatus(false, "写入失败");
                } else {
                    this.setCmdStatus(true, "写入成功");
                }
            } else {
                throw new PlcException("错误的功能码");
            }
        }
    }

    /**
     * 批量读取
     * @see ReadAddress#getAddress() M100, I100, Q100, DB1.100
     * @param batchAddress
     * @return
     */
    @Override
    protected List<byte[]> doRead(List<ReadAddress> batchAddress) {
        List<byte[]> result = new ArrayList<>();
        int start = 0;
        if(this.data != null) {
            for (int index=0; index < batchAddress.size(); index++) {
                ReadAddress address = batchAddress.get(index);

                byte[] bytes = ByteUtil.subBytes(this.data, start, start + address.getLength());

                if(address.getType() == AddressType.Bit) {
                    start += address.getLength() + 5;
                } else {
                    start += address.getLength() + 4;
                }

                result.add(bytes);
            }
        }

        return result;
    }

    @Override
    protected short calcBitLength(short length) {
        return (short) (length / 8 + (length % 8 > 0 ? 1 : 0)) ;
    }

    @Override
    protected boolean[] bytesToBooleans(byte[] bytes, short length) {
        boolean[] booleans = PLCUtils.byteToBoolArray(bytes);
        return PLCUtils.copeArray(booleans, 0, length);
    }

    @Override
    protected Class<SiemensS7Message> getMessageClass() {
        return SiemensS7Message.class;
    }

    @Override
    public PlcProtocolType protocolType() {
        return PlcProtocolType.SiemensS7;
    }

    @Override
    public DataTransfer getDataTransfer() {
        return SiemensDataTransfer.getInstance();
    }

    @Override
    public void writeFull(byte[] fullMessage) {
        this.requestMessage = new SiemensS7Message(new SiemensMessageHeader(fullMessage));
        this.sync(this.getTimeout()).request();
    }

    @Override
    public byte[] readFull(byte[] fullMessage) {
        this.requestMessage = new SiemensS7Message(new SiemensMessageHeader(fullMessage));
        this.sync(this.getTimeout()).request();
        return this.data;
    }

    @Override
    public void write(String address, boolean[] value) {
        byte[] bytes = ByteUtil.boolArrayToByte(value);
        this.write(address, bytes);
    }

    public byte[] getData() {
        return data;
    }
}
